首页 / 技术类 / C++ / 将 Timer 对象化

将 Timer 对象化

2013-06-25 00:18:00

Timer这玩意儿很常用,却又很烦人。烦人之处有四:

就我个人而言,我喜欢2.a特性,因此着眼于解决2.b问题。

回调函数成员化,看着好像很眼熟。不错,我们曾经在《学习下WTL的thunk》里面干过这事情。因此几个月前我就觉得对于Timer也是可以做到的,但由于各种原因没时间去弄,同时也很遗憾前公司“架构师”没有采用这种方案。

今天刚刚打眼到公司有人也做了这件事(1.a + 2.b 模式),趁目前还没去很仔细地去研究,赶紧自己先写一个差异化版本,以避免不必要的版权纠纷^_^

Thunk需要占用一个正常参数。我们观察一下Timer的回调函数格式:

1VOID CALLBACK TimerProc(
2    _In_  HWND hWnd,
3    _In_  UINT uMsg,
4    _In_  UINT_PTR idEvent,
5    _In_  DWORD dwTime
6);

很不错,前面三个参数几乎都是没用的(至少第一个是没用的,这就够了)。

先把原先为了WNDPROC的Thunk改得通用些,WNDPROC改成LPVOID或者模版化,所有出现“Wnd”的地方都去掉“Wnd”字样,改完后变成: http://xllib.codeplex.com/SourceControl/latest#SourceCode/xl/Win32/GUI/xlThunk.h

然后写Timer的实现。代码比较短,我先全贴了:

 1typedef Function<void (DWORD dwTime)> TimerCallback;
 2
 3class Timer
 4{
 5public:
 6    Timer() : m_uTimerId(0)
 7    {
 8      
 9    }
10
11    ~Timer()
12    {
13        Kill();
14    }
15
16public:
17    bool Set(UINT uElapse, TimerCallback fnCallback)
18    {
19        if (m_uTimerId != 0)
20        {
21            return false;
22        }
23
24        m_fnCallback = fnCallback;
25        m_thunk.SetObject(this);
26        m_thunk.SetRealProc(StaticTimerProc);
27
28        m_uTimerId = SetTimer(nullptr, 0, uElapse, m_thunk.GetThunkProc());
29
30        if (m_uTimerId == 0)
31        {
32            return false;
33        }
34
35        return true;
36    }
37
38    void Kill()
39    {
40        if (m_uTimerId != 0)
41        {
42            KillTimer(nullptr, m_uTimerId);
43            m_uTimerId = 0;
44        }
45    }
46
47protected:
48    static VOID CALLBACK StaticTimerProc(HWND hWnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
49    {
50        return ((Timer *)hWnd)->m_fnCallback(dwTime);
51    }
52
53protected:
54    UINT_PTR m_uTimerId;
55    Thunk<TIMERPROC> m_thunk;
56    TimerCallback m_fnCallback;
57};

注意Timer::Set里面,设好Thunk的数据以后,直接把Timer创建在Thunk上就可以了,比起窗口那个处理干净利落多了。咦?窗口里为什么要搞个StartProc,然后再在StartProc里把回调函数设到Thunk上呢?

是这样的,注册窗口类的时候就需要一个回调函数,此时窗口未创建。CreateWindow的过程中,会调用到回调函数(WM_CREATE),如果没有特殊处理,需要调用回DefWindowProc,其第一个参数是HWND,而我们此时如果使用Thunk的话,就会篡改掉系统调用回调函数时给出的HWND,从而没法正确调用DefWindowProc。也就是说,如果第一次被调用需要使用第一个参数的,就需要像窗口的处理一样,搞个StartProc第一次用。

这里我们使用SetTimer(NULL, ...),这第一个参数任何时候都不需要使用,所以可直接将Timer创建在Thunk上。

用例:

 1int main()
 2{
 3    xl::Timer t;
 4    t.Set(1000, [](DWORD dwTime)
 5        {
 6            printf("%u\n", dwTime);
 7        });
 8
 9    MSG msg = {};
10
11    while (GetMessage(&msg, nullptr, 0, 0))
12    {
13        TranslateMessage(&msg);
14        DispatchMessage(&msg);
15    }
16
17    return 0;
18}

运行结果:

源代码见: http://xllib.codeplex.com/SourceControl/latest#SourceCode/xl/Win32/Timer/xlTimer.h

流浪了近一个月,我又开始上班啦!


首发:http://www.cppblog.com/Streamlet/archive/2013/06/25/201279.html



NoteIsSite/0.4